﻿using System;
using System.Collections.Generic;
using System.Reflection;
using KuiHuaBaoDian;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Pool;
using UnityEngine.UI;

public static class GameObjectUtility {

    public static void SetLayerRecursively(this GameObject gameObject, int layer) {
        Assert.IsNotNull(gameObject);

        gameObject.layer = layer;
        foreach (Transform transform in gameObject.transform) {
            transform.gameObject.SetLayerRecursively(layer);
        }
    }
}

public static class TransformUtility {

    public static void ResetLocalMatrix(this Transform transform) {
        Assert.IsNotNull(transform);

        transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
        transform.localScale = Vector3.one;
    }

    public static void DestroyChildren(this Transform transform, bool includeIgnore = false) {
        Assert.IsNotNull(transform);

        for (int i = transform.childCount - 1; i >= 0; i--) {
            var child = transform.GetChild(i);
            if (includeIgnore || !child.gameObject.TryGetComponent<LayoutElement>(out var layoutElement) || !layoutElement.ignoreLayout) {
                UnityEngine.Object.Destroy(child.gameObject);
            }
        }
    }
}

public static class ReadOnlyListUtility {

    public static int IndexOf<T>(this IReadOnlyList<T> self, T elementToFind) {
        int i = 0;
        foreach (T element in self) {
            if (Equals(element, elementToFind))
                return i;
            i++;
        }
        return -1;
    }
}


public static class AutowireUtility {

    private static readonly Type TYPE_OBJECT = typeof(object);

    private static readonly IDictionary<Type, MemberInfo[]> m_MemberInfosDictionary = new Dictionary<Type, MemberInfo[]>();
    private static readonly IDictionary<PropertyInfo, PropertyInfo> m_PropertyDeclaringDictionary = new Dictionary<PropertyInfo, PropertyInfo>();
    private static readonly IDictionary<FieldInfo, FieldInfo> m_FieldDeclaringDictionary = new Dictionary<FieldInfo, FieldInfo>();

    private static readonly IDictionary<Type, MemberInfo[]> m_AutowiredMemberInfosDictionary = new Dictionary<Type, MemberInfo[]>();

    public static void Autowire(IEntity instance, Action<object, MemberInfo> onMemberInstantiated = null) {
        if (!Application.isPlaying) {
            return;
        }
        Assert.IsNotNull(instance);

        var type = instance.GetType();
        var autowiredMemberInfoList = m_AutowiredMemberInfosDictionary.ContainsKey(type) ? null : ListPool<MemberInfo>.Get();
        while (type != null && type != TYPE_OBJECT && KHBD.Context.IsApplicationPlaying) {
            var cacheMemberInfoList = m_MemberInfosDictionary.TryGetValue(type, out var memberInfos) ? null : ListPool<MemberInfo>.Get();
            if (cacheMemberInfoList != null) {
                memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            }
            foreach (var memberInfo in memberInfos) {
                if (memberInfo.MemberType == MemberTypes.Property || memberInfo.MemberType == MemberTypes.Field) {
                    if (cacheMemberInfoList == null) {
                        CreateMember(type, memberInfo, instance, autowiredMemberInfoList, onMemberInstantiated);
                    } else {
                        var autowiredAttribute = memberInfo.GetCustomAttribute<AutowiredAttribute>();
                        //Debug.Log($"{nameof(AutowireUtility)}.{nameof(Autowire)}({nameof(instance)} = {instance}, {nameof(memberInfo)}.{nameof(memberInfo.Name)} = {memberInfo.Name}, {nameof(memberInfo.DeclaringType)} = {memberInfo.DeclaringType.FullName}, {nameof(autowiredAttribute)} = {autowiredAttribute})");
                        if (autowiredAttribute != null) {
                            try {
                                CreateMember(type, memberInfo, instance, autowiredMemberInfoList, onMemberInstantiated);
                                cacheMemberInfoList.Add(memberInfo);
                            } catch (Exception e) {
                                Debug.LogException(e);
                            }
                        }
                    }
                }
            }
            if (cacheMemberInfoList != null) {
                m_MemberInfosDictionary.Add(type, cacheMemberInfoList.ToArray());
                ListPool<MemberInfo>.Release(cacheMemberInfoList);
            }
            type = type.BaseType;
        }
        if (autowiredMemberInfoList != null) {
            m_AutowiredMemberInfosDictionary.Add(instance.GetType(), autowiredMemberInfoList.ToArray());
            ListPool<MemberInfo>.Release(autowiredMemberInfoList);
        }
    }

    private static void CreateMember(Type type, MemberInfo memberInfo, object instance, IList<MemberInfo> autowiredMemberInfoList, Action<object, MemberInfo> onMemberInstantiated = null) {

        object value;
        if (memberInfo is PropertyInfo propertyInfo) {
            if (!m_PropertyDeclaringDictionary.TryGetValue(propertyInfo, out var declaring)) {
                declaring = m_PropertyDeclaringDictionary[propertyInfo] = memberInfo.DeclaringType == type ? propertyInfo : memberInfo.DeclaringType.GetProperty(memberInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            }
            value = CreateInstance(declaring.PropertyType);
            declaring.SetValue(instance, value);
        } else if (memberInfo is FieldInfo fieldInfo) {
            if (!m_FieldDeclaringDictionary.TryGetValue(fieldInfo, out var declaring)) {
                declaring = m_FieldDeclaringDictionary[fieldInfo] = memberInfo.DeclaringType == type ? fieldInfo : memberInfo.DeclaringType.GetField(memberInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            }
            value = CreateInstance(declaring.FieldType);
            declaring.SetValue(instance, value);
        } else {
            throw new InvalidOperationException();
        }
        //Debug.Log($"{nameof(Autowire)}({nameof(instance)} = {instance}, {nameof(value)} = {value})");
        autowiredMemberInfoList?.Add(memberInfo);
        onMemberInstantiated?.Invoke(value, memberInfo);
    }

    private static object CreateInstance(Type interfaceType) {
        return KHBD.TypeMap.Instantiate(interfaceType);
    }

    public static void Dispose(IEntity instance) {
        Assert.IsNotNull(instance);
        if (m_AutowiredMemberInfosDictionary.TryGetValue(instance.GetType(), out var autowiredMemberInfos)) {
            foreach (var memberInfo in autowiredMemberInfos) {
                object value = null;
                if (memberInfo is PropertyInfo propertyInfo) {
                    value = propertyInfo.GetValue(instance);
                    propertyInfo.SetValue(instance, null);
                } else if (memberInfo is FieldInfo fieldInfo) {
                    value = fieldInfo.GetValue(instance);
                    fieldInfo.SetValue(instance, null);
                }
                if (value is IDisposable disposable) {
                    //if (value is IPoolObject poolObject) {
                    //    UnityEngine.Debug.Log($"[{instance.GetType().FullName}] dispose {memberInfo.MemberType} [{value.GetType().FullName}] {memberInfo.Name}");
                    //}
                    disposable.Dispose();
                }
            }
        }
    }
}

public static class DelegateUtilities {

    public static void Clear(this Delegate @delegate) {
        if (@delegate != null) {
            Delegate.RemoveAll(@delegate, @delegate);
        }
    }
}